#include "NodeItem.h"
#include "xMappingNodeTypeLibrary.h"
#include "AbstractMappingWindow.h"
#include "xNodeType_Math.h"
#include "xNodeType_StringComposer.h"
#include "xNodeType_composite.h"
#include "NodeInPort.h"
#include "NodeOutPort.h"
#include "Factory_xNodeType.h"
#include "xInMappingPort.h"
#include "AbstractManager.h"

#include <QtGui/QPen>
#include <QtGui/QBrush>
#include <QtGui/QColor>
#include <QtGui/QPainter>
#include <QtGui/QGraphicsScene>
#include <QtGui/QKeyEvent>
#include <QtGui/QTreeWidget>
#include <QtGui/QMenu>
#include <QtGui/QGraphicsSceneMouseEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QInputDialog>
#include <QtGui/QMessageBox>
#include <QtCore/QVariant>

using namespace std;

NodeItem::NodeItem(string name, xMappingTree* xmt, AbstractManager* manager )
	:AbstractShapeItem(name, manager)
{
	xMappingNodeTypeLibrary *lib=xMappingNodeTypeLibrary::getInstance();
	xMappingNodeTypeDescriptor *des=lib->get_xmnt_descriptor(name);

	xNodeType* nodeType = Factory_xNodeType::create_xnodetype(name);
	_mpnNode = xmt->create_node(nodeType);

	initNode(des->_category.c_str());
	this->setupPort();
}

NodeItem::NodeItem( xMappingNode* mappingNode , AbstractManager* manager )
	:AbstractShapeItem(mappingNode->getNodeTypePtr()->getName(), manager)
{
	this->_mpnNode = mappingNode;

	xMappingNodeTypeLibrary *lib=xMappingNodeTypeLibrary::getInstance();
	xMappingNodeTypeDescriptor *des = lib->get_xmnt_descriptor(_mpnNode->getNodeTypePtr()->getName());

	initNode(des->_category.c_str());
	this->setupPort();
}

NodeItem::NodeItem( DIVA_VM * vm, xMappingTree * xmt, AbstractManager * manager ):AbstractShapeItem("existing_vm", manager) {
	Factory_parameter param( vm );;
	xMappingNodeTypeLibrary *lib=xMappingNodeTypeLibrary::getInstance();
	xMappingNodeTypeDescriptor *des = lib->get_xmnt_descriptor( "existing" );

	xNodeType* nodeType = Factory_xNodeType::create_xnodetype("existing", param);
	_mpnNode = xmt->create_node(nodeType);

	initNode(des->_category.c_str());
	this->setupPort();
}

NodeItem::~NodeItem(void)
{
}


void NodeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{

	int x = 0;
	int y = 0;


	if ( isForeachElements() ){
		for ( int i = 3; i > 0; --i )
		{
			//_nameColor.setAlpha(200-50*i);
			painter->setPen(QColor("#999999"));
			painter->setBrush(QBrush(_nameColor));
			painter->drawRoundedRect(x+3*i, y+3*i, WIDTH, NODE_NAME_HEIGHT + this->_bodyHeight , ROUND_RADIUS, ROUND_RADIUS);
		}
	}

	// 繪製顯示名稱部分(上半部)色塊
	painter->setPen(QPen(Qt::NoPen));
	_nameColor.setAlpha(255);
	painter->setBrush(QBrush(_nameColor));
	painter->drawRoundedRect(x, y, WIDTH, NODE_NAME_HEIGHT + OVERLAP_HEIGHT , ROUND_RADIUS, ROUND_RADIUS);

	// 繪製顯示各port部分(下半部)色塊
	painter->setPen(QPen(Qt::NoPen));
	painter->setBrush(QBrush(_bodyColor));
	y = NODE_NAME_HEIGHT;
	painter->drawRoundedRect(x, y,WIDTH, this->_bodyHeight ,  ROUND_RADIUS, ROUND_RADIUS);

	// 繪製node名稱
	QFont sansFont("Helvetica", 9);
	sansFont.setBold(true);
	painter->setFont(sansFont);
	painter->setPen(QPen(Qt::white));
	y = 0;
	painter->drawText(x, y, WIDTH, NODE_NAME_HEIGHT,Qt::AlignCenter, this->_name.c_str());

	this->postPaint( painter, option, widget );

	// TODO move this method to a new class, which overides postPaint
	// 如果是 node 是 string_composer type，如果已經有設定了string，則顯示一個小圖示來提示使用者
	if (this->_mpnNode->getNodeTypePtr()->getType() == "string_composer"){
		xNodeType_StringComposer* stringComposerNode;
		stringComposerNode = dynamic_cast<xNodeType_StringComposer*>(this->getMappingNode()->getNodeTypePtr());
		assert(stringComposerNode);

		if (stringComposerNode->haveCustomString()){
			QImage image(":/resource/script-pencil-icon.png");
			painter->drawImage(x + 125, y + 5, image);
		}

	}

	// 如果 node 被選取到，顯示選取框來告知使用者
	if ( this->isSelected()) {
		painter->setBrush(QBrush(Qt::NoBrush));
		painter->setPen(QPen("#AEEEEE"));
		painter->drawRoundedRect( x-7, y-5 , WIDTH + 12, NODE_NAME_HEIGHT + this->_bodyHeight + 10, ROUND_RADIUS, ROUND_RADIUS );
	}
}

QRectF NodeItem::boundingRect() const
{
	int x = 0;
	int y = 0;

	if ( this->_mpnNode->isForeachElements() ){
		return QRectF(x-7, y-5, WIDTH + 24, NODE_NAME_HEIGHT + this->_bodyHeight + 22);
	}

	return QRectF(x-7, y-5, WIDTH + 12, NODE_NAME_HEIGHT + this->_bodyHeight + 10);
}

void NodeItem::createColor( string category )
{
	QColor UBVM_COLOR(47, 86, 9);
	QColor REFERENCE_COLOR(81, 4, 87);
	QColor COMPOSITE_COLOR(81, 4, 87);
	QColor BASIC_COLOR(179, 160, 9);
	QColor COMPOSITE_TOOLS_COLOR(86, 85, 0);
	QColor AUXILIARY_COLOR(0, 104, 162);
	QColor ARITH_COLOR(0, 104, 162);


	if ( category == "ubvm" ) {
		_nameColor = UBVM_COLOR.lighter(120);
		_bodyColor = UBVM_COLOR;
	} else if ( category == "reference" ) {
		_nameColor = REFERENCE_COLOR.lighter(120);
		_bodyColor = REFERENCE_COLOR;
	} else if ( category == "composite" ) {
		_nameColor = COMPOSITE_COLOR.lighter(120);
		_bodyColor = COMPOSITE_COLOR;
	} else if ( category == "basic" ) {
		_nameColor = BASIC_COLOR.lighter(120);
		_bodyColor = BASIC_COLOR;
	} else if ( category == "tools" ) {
		_nameColor = COMPOSITE_TOOLS_COLOR.lighter(120);
		_bodyColor = COMPOSITE_TOOLS_COLOR;
	} else if ( category == "auxiliary" ) {
		_nameColor = AUXILIARY_COLOR.lighter(120);
		_bodyColor = AUXILIARY_COLOR;
	} else if ( category == "arith" ) {
		_nameColor = ARITH_COLOR.lighter(120);
		_bodyColor = ARITH_COLOR;
	}
}

void NodeItem::resetSizeAndPortPosition()
{
	this->prepareGeometryChange();	// 改變node大小(boundingRect)時要呼叫prepareGeometryChange()，否則在刪除物件時會產生殘影
	this->setPortPosition();		// 設定每個port的位置
	this->updateLinePosition();		// 因為port位置改變了，所以也必須要重劃所有的link

	this->scene()->update();		// 重畫Node，這裡不能只呼叫 _currentItem的update，因為這樣只會在boundingRect()回傳範圍內重畫
									// 但是因為Node有可能縮小，縮小後的boundingRect()比較小，就會留下縮小之前部分的殘影，所以要呼叫
									// scene 的update()來重畫畫布裡面所有的範圍
}


void NodeItem::mousePressEvent( QGraphicsSceneMouseEvent* mouseEvent)
{
	QGraphicsItem::mousePressEvent(mouseEvent);
}


void NodeItem::mouseReleaseEvent( QGraphicsSceneMouseEvent * mouseEvent)
{
	emit itemSelected(this);
	QGraphicsItem::mouseReleaseEvent(mouseEvent);
	
}


void NodeItem::keyPressEvent( QKeyEvent* event)
{
	QGraphicsItem::keyPressEvent(event);
}


vector<bool>& NodeItem::getTreeItemExpandedState( QString type )
{
	if ( type == "inportTree") {
		return this->_inportTreeItemExpandedState;
	} else if( type == "outportTree") {
		return this->_outportTreeItemExpandedState;
	}  else if( type == "attributeTree" ) {
		return this->_attributeTreeItemExpandedState;
	} else {
		// TODO throw an exception
		assert( !"invalid type" );
		return *( static_cast< vector<bool> * >( NULL ) );
	}
}

void NodeItem::setTreeItemExpandedState( QString type, int index, bool isExpand)
{
	if ( type == "inportTree") {
		this->_inportTreeItemExpandedState.at(index) = isExpand;
	} else if( type == "outportTree") {
		this->_outportTreeItemExpandedState.at(index) = isExpand;
	}  else if( type == "attributeTree" ) {
		this->_attributeTreeItemExpandedState.at(index) = isExpand;
	} else {
		assert(false);
	}
}


QVariant NodeItem::itemChange( GraphicsItemChange change, const QVariant &value )
{
	if (change == QGraphicsItem::ItemPositionChange && scene() ) {
		updateLinePosition();	// 當item移動，更新所有連接在item上的line的位置

		// 限制 item 只能在畫布裡面移動，不能移出畫布外面
		// 方法是去測試 item 位置是否在 sceneRect 裡面，如果不是的話就重新設定 item 位置 (左上角)
		// 因為是測試左上角，所以當 item 移到畫布右下的時候還是會跑出去 (因為那時候左上角還在畫布裡面)
		// 解決方法是把 sceneRect 長寬都剪掉 item 的長寬
		QPointF newPos = value.toPointF();
		QRectF rect = scene()->sceneRect();

		rect.setWidth( rect.width() - this->width());
		rect.setHeight( rect.height() - this->height() );

		if( !rect.contains(newPos) ){
			newPos.setX( qMin( rect.right() ,qMax(newPos.x(), rect.left())) );
			newPos.setY( qMin( rect.bottom() ,qMax( newPos.y(), rect.top())) );
			return newPos;
		} 

		// 當 node 移進或移出 foreach area 時，更改 node isForeach 屬性
		this->setForeachElements(_manager->isInForEachRect(this));

		// 因為 composite node 包含不只一個 mapping node，所以要更改所有的 mapping node 的 isForeachElement
		xNodeType_composite* nodeType = dynamic_cast<xNodeType_composite*>(this->_mpnNode->getNodeTypePtr());
		if ( nodeType != NULL)
		{
			std::vector<xMappingNode*> nodes = nodeType->getxMappingTree()->get_nodes();
			std::vector<xMappingNode*>::iterator itr;
			for ( itr = nodes.begin(); itr != nodes.end(); ++itr )
			{
				(*itr)->setForeachElements(isForeachElements());
			}
		}
	}

	if ( change == QGraphicsItem::ItemSelectedChange ){
		if ( !value.toBool() ){
			emit itemDeSelected();
		} 
	}
	return QGraphicsItem::itemChange( change, value );
}

void NodeItem::updateLinePosition()
{
	for ( int i = 0; i < _inportList.size(); ++i )
	{
		_inportList.at(i)->updateLinePosition();
	}
	for ( int i = 0; i < _outportList.size(); ++i )
	{
		_outportList.at(i)->updateLinePosition();
	}
}

NodeInPort* NodeItem::addInPort()
{
	NodeInPort* inport = AbstractShapeItem::addInPort();
	this->resetSizeAndPortPosition();
	this->setPortListChanged(true);
	return inport;
}

NodeOutPort* NodeItem::addOutPort()
{
	NodeOutPort* outport = AbstractShapeItem::addOutPort();
	outport->setDrawName( true );
	this->resetSizeAndPortPosition();
	this->setPortListChanged(true);
	return outport;
}

void NodeItem::deleteInPort( NodeInPort* inPort)
{
	AbstractShapeItem::deleteInPort(inPort);
	this->resetSizeAndPortPosition();
	this->setPortListChanged(true);
}


void NodeItem::deleteOutPort( NodeOutPort* outPort)
{
	AbstractShapeItem::deleteOutPort(outPort);
	this->resetSizeAndPortPosition();
	this->setPortListChanged(true);
}


void NodeItem::setupPort()
{
	for(int i = 0; i <_mpnNode->getInportSize(); ++i){

		xInMappingPort* imp = _mpnNode->getInPort(i);
		if (this->_name =="math"){
			std::ostringstream oss;
			oss << trf(imp->getName()) << " $" <<i;
			imp->setName(oss.str());
		}
		_inportList.push_back(new NodeInPort(imp,this));
	}

	for(int i = 0; i< _mpnNode->getOutportSize(); ++i){

		xOutMappingPort* omp = _mpnNode->getOutPort(i);
		auto outport = new NodeOutPort(omp,this);
		_outportList.push_back( outport );
		outport->setDrawName( true );
	}

	this->setPortPosition();
}

void NodeItem::setPortPosition()
{
	vector<NodeInPort*>::iterator inPortItr;
	int i = 0;

	for( inPortItr=_inportList.begin(); inPortItr!=_inportList.end(); ++inPortItr ){
		if( ( *inPortItr)->isVisible() ){
			++i;
			(*inPortItr)->setY( NodeItem::NODE_NAME_HEIGHT + 15*i );
		}
	}
	int visibleNumber = i;

	std::vector<NodeOutPort*>::iterator OutPortitr;
	i=0;
	for( OutPortitr=_outportList.begin(); OutPortitr!=_outportList.end(); ++OutPortitr){
		if( (*OutPortitr)->isVisible() ){
			++i;
			(*OutPortitr)->setY( NodeItem::NODE_NAME_HEIGHT + 15*i );
		}
	}

	// 判斷inPort outPort哪一邊個數較多，以較多的一方來決定Node高度
	if( i >= visibleNumber ){
		visibleNumber = i;
	}

	this->setBodyHeight( NodeItem::NODE_BODY_HEIGHT + visibleNumber*15 );	// 根據有幾個port來調整NOde高度
}

bool NodeItem::isPortListChanged()
{
	return this->_portListChanged;
}

void NodeItem::setPortListChanged( bool change )
{
	this->_portListChanged = change;

	if ( change == true) {
		emit portListChanged(this);
	}
	
}

void NodeItem::editFormula()
{
	// 假如還沒有拉link就想要輸入formula，跳出警告視窗請使用者先拉link
	if (!this->_inportList.at(0)->isConnected()){
		QMessageBox::critical( _manager->getMappingDialog(),
			tr("Error"),
			tr("Inport need to link variables"));
		
		return;
	} 

	xNodeType_Math* arithNode;
	string oldFormula = "";

	arithNode = dynamic_cast<xNodeType_Math*>(this->getMappingNode()->getNodeTypePtr());
	assert(arithNode);

	// 檢查是否已經有輸入過算式
	if (arithNode->haveFormula()){
		oldFormula = arithNode->getFormula();
	}

	// pop 出一個 input dialog 讓使用者寫入算式
	bool ok;
	QString formula = QInputDialog::getText( _manager->getMappingDialog(),
									tr("Key the arithmetic"),
									tr("Formula"),
									QLineEdit::Normal,
									oldFormula.c_str(),
									&ok).trimmed();

	// 如果使用者按下 ok 且輸入內容不為空白，就把算式交給 xNodeType_Math::isFormulaValidate()來檢查算式是否合法
	// isFormulaValidate()回傳 TRUE 表示合法並寫入算式，如果回傳其他字串表示不合法
	if ( ok && !formula.isEmpty()) {
		string result = arithNode->isFormulaValidate(formula.toStdString(),true);

		if ( result != "TRUE" ) {
			QMessageBox::critical( _manager->getMappingDialog(),
								   tr("Illegal formula"),
								   result.c_str());
		}
	} else if( !ok ) {
		// do nothing because user canceled editing
	} else if ( formula.isEmpty() ) {
		arithNode->removeFormula();
	}
}

void NodeItem::editCustomString()
{
	xNodeType_StringComposer *stringComposerNode;
	string oldString = "";

	stringComposerNode = dynamic_cast<xNodeType_StringComposer*>(this->getMappingNode()->getNodeTypePtr());
	assert(stringComposerNode!=NULL);

	if ( stringComposerNode->haveCustomString() ){
		oldString = stringComposerNode->getCustomString();
	}


	// pop 出一個 input dialog 讓使用者寫入字串
	bool ok;
	QString customString = QInputDialog::getText( _manager->getMappingDialog(),
		tr("Key the custom string"),
		tr("Custom string"),
		QLineEdit::Normal,
		oldString.c_str(),
		&ok).trimmed();

	// 如果使用者按下 ok ，把字串交給 xNodeType_StringComposer::isStringValidate()來檢查是否合法
	if( ok && !customString.isEmpty()){
		string result = stringComposerNode->isStringValidate(customString.toStdString());

		if ( result != "TRUE" ){
			QMessageBox::critical( _manager->getMappingDialog(),
				tr("Illegal String"),
				result.c_str());
		} 
	} else if ( customString.isEmpty()){
		stringComposerNode->removeCustomString();
	}
}

void NodeItem::createActions()
{
	_deleteAction = new QAction(QIcon(":resource/cross-small-icon.png"), "Delete Node",this);
	connect(_deleteAction, SIGNAL(triggered()), this, SLOT(remove()));

	// 如果是 math node，則多一個 edit formula 的動作
	if (this->_mpnNode->getNodeTypePtr()->getType() == "math") {
		_editFormulaAction = new QAction(QIcon(":resource/calculator-pencil-icon.png"),"Key the arithmetic",this);
		connect( _editFormulaAction, SIGNAL(triggered()), this, SLOT(editFormula()));
	} 
	// 如果是 string composer node，則多一個 edit custom string 的動作
	else if(this->_mpnNode->getNodeTypePtr()->getType() == "string_composer"){
		_editCustomStringAction = new QAction(QIcon(":resource/edit-signiture-icon.png"),"Key the custom string",this);
		connect( _editCustomStringAction, SIGNAL(triggered()),this, SLOT(editCustomString()));
	}
}

void NodeItem::initMenu()
{
	_contextMenu = new QMenu();
	_contextMenu->addAction(_deleteAction);

	if (this->_mpnNode->getNodeTypePtr()->getType() == "math") {
		_contextMenu->addSeparator();
		_contextMenu->addAction(_editFormulaAction);
	} else if (this->_mpnNode->getNodeTypePtr()->getType() == "string_composer") {
		_contextMenu->addSeparator();
		_contextMenu->addAction(_editCustomStringAction);
	}
	
}

void NodeItem::contextMenuEvent( QGraphicsSceneContextMenuEvent* event)
{
	this->setSelected(true);
	this->createActions();
	this->initMenu();
	_contextMenu->popup(event->screenPos());
}

void NodeItem::remove()
{
	this->_manager->deleteNode(this);
}

int NodeItem::height()
{
	return this->_bodyHeight + NODE_NAME_HEIGHT;
}

void NodeItem::removeAllLinks()
{
	this->_manager->removeAllLinks(this);
}

void NodeItem::initNode( std::string category )
{
	this->_portListChanged = false;
	this->_width = WIDTH;
	this->_bodyHeight = NODE_BODY_HEIGHT;
	this->_nodeType = AbstractShapeItem::MAPPING_NODE;

	this->setForeachElements(false);

	this->createColor(category);	// 依照分類來產生顏色

	setFlag(QGraphicsItem::ItemIsMovable, true);	// node可以移動
	setFlag(QGraphicsItem::ItemIsSelectable, true);	// node可以被選取
	setFlag(QGraphicsItem::ItemIsFocusable, true);	// 讓node可以接收到鍵盤事件 (keyPressEvent())
	setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);	// 當node位置改變的時候 QGraphicsItem::ItemPositionChange 這個enum會被送出 (itemChanged())

	this->_inportTreeItemExpandedState.assign(CATEGORY_NO, false);
	this->_outportTreeItemExpandedState.assign(CATEGORY_NO, false);
	this->_attributeTreeItemExpandedState.assign(CATEGORY_NO, false);
}

void NodeItem::postPaint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget ) {
}

bool NodeItem::validate() const {
	return true;
}

NodeInPort * NodeItem::getInPortByName( const std::string & name ) const {
	auto it = std::find_if( this->_inportList.begin(), this->_inportList.end(), [&name]( NodeInPort * p )->bool {
		return p->getName() == name;
	} );
	if( it != this->_inportList.end() ) {
		return *it;
	}
	return nullptr;
}
